Introduction

This document provides a demonstration of conversion of text into a document matrix, and the application of a number of techniques on the resulting matrix.

Reading the raw data

In this example the data has the csv format with headings

In reading the data we will associate the row id, and will retain the app_tag. The transcription will be converted into a tf-idf matrix using a simple tokenisation. The tf-idf is calculated largely from the components of

Term Frequency

\[ tf(t,d) = 0.5 + 0.5\times\frac{f_{t,d}}{max(f_{t',d}:t'\in d)} \] For term \(t\) in given document \(d\) to produce frequency for the document \(f_{t,d}\).

Inverse Document Frequency \[ idf(t,D) = \log\left( \frac{N}{|d \in D: t \in d|}\right) \]

with \(N\) being the total number of documents, that contain each term \(t\).

Term Frequency Inverse Document Frequency

\[ tfidf(t,d) = tf(t,d)\times idf(t,D) \]

The TF-IDF matrix is then used as the input to the procedure.

require(dplyr)
source("preprocess_text.R")
source("prepare_svd_document_model.R")
source("svd_search.R")
file <- "input/csexample/tagging_data.csv"
outputBase <- "data/csexample/"  
data_cols <- c("row", "transcription", "occurrence", "do", "what", "sem_tag", "app_tag")
id_cols <- c("row")
# note this is used to generate the document term matrix from scratch and is commented out
# for subsequent runs after initially used to build the document term matrix.
#prepare_document_model(file, data_cols, id_cols, outputBase, text_col="transcription")

After generating the original model we read it back in and use it to explore the data.

data <- load_document_data(outputBase)
doc_mat <- load_document_term_mat(outputBase)
  unique_words <- load_unique_words(outputBase)
  searchA <- load_search_space(outputBase)
  
  searchU <- searchA$u
  searchS <- searchA$d
  # transposed relation of attributes to objects rows equal the attributes
  searchV <- searchA$v

The system that is generated is a matrix decomposition produced by an SVD. This is described as a decomposition of the \(m \times n\) document-term matrix \(A\) such that

\[ A = USV' \]

Where

Note that the matrix \(A\) is standardised prior to performing the decomposition.

The \(S\) matrix is also later converted into a diagonal matrix \(\Sigma = IS^{1/2}\) which is used in projecting matrices into a search space.

There are a number of representations in the linear space that results that may be useful.

Note also that \(S\) can be approximated as.

\[ S = U' A V \]

The total variation explained by each component of the decomposition can be determined by interpreting \(S\) as the standard deviation and squaring it to determine the variance, the proportion of variance can be defined as

\[ f_k = \frac{s_{k}^2}{\sum_{i=1}^r s_i^2} \]

The measure of the total entropy (or amount of disorder in a set of objects) in the design matrix \(A\) can be calculated as

\[ entropy = \frac{-1}{\log r} \sum_{k=1}^r \log (f_k) \]

A scree plot can be used to visualise the contribution \(f_k\) to total variation in the data set for each component.

total <- sum(searchS^2)
eigenV <- searchS^2
plot(eigenV, type="b")

plot(eigenV/total, type="b")

The first component explains close to 100% of all variation, the corresponding eigenvectors can be considered as being a linear combination of all terms, note that due to the high amount of variation explained, it is difficult to determine the contribution of the other components from the second component onwards.

We can either estimate this as a proportion of the remaining fraction or plot the proportion of the total variation.

The second components onwards are plotted as follows.

plot(eigenV[2:100], type="b")

plot(eigenV[2:100]/total, type="b")

If we were looking for the technique to yield a high amount of compression this suggests that there is little more than the first few linear combinations would be required from the projection of \(AU\) or \(AV'\) to effectively summarise the data, and in fact we will use this compression technique later on during classification.

Where dimensionality reduction is required, it is also possible to use the subsets \(k\) to compute reduced correlation matrices, that are of less dimension than \(AA'\) and \(A'A\) these are the truncated correlation matrices and can be calculated as:

\[ A_k A_k' = U_k (S_k^2) U_k' \]

similarly

\[ A_k'A_k = V_k S_k^2 V_k' \]

The resulting correlation matrices are lower dimensional representations of the original correlation matrix. These may be useful where lower dimensional approximation of \(A_k\) is used in a distribution requiring correlation matrix as a parameter.

Note also that the scree plot does not show the total number of components as the remainder become increasingly small, and therefore insignificant.

The total dimensions of the raw term document matrix is as follows:

dim(doc_mat)
[1] 11002  1607

That is we have a relatively small vocabulary in this data set, there are only 1607 terms, however we have just over 11 thousand utterances in total.

We can visualise the term vector space as an ordination using the projection of the terms into the vector space and then rendering the first two components. We will use the vector space scaled by the square root of each singular value which is the search space that is used when projecting term vectors into the term space, note that a similar search space is used when performing queries against the term matrix. The search space is formed by the diagonal matrix \(S_s = IS^{1/2}\) (this is also denoted \(\Sigma\) in articles on LSI) and the product of the search space for the terms \(V_s = VS_s\).

n <- length(searchS)
temp <- rep(0, n*n)
Ss <- matrix(temp, nrow=n, ncol=n)
diag(Ss) <- sqrt(searchS)
Vp <- (as.matrix(searchV))
Vs <- (Vp%*%Ss)
x <- Vs[,1]
y <- Vs[,2]
# what if we project the correlation matrix X'X for terms into the eigenvectors Vp
X <- as.matrix(doc_mat)
X <- scale(X)
C <- t(X)%*%X
Zv <- C%*%Vs
dim(Zv)
[1] 1607 1607
x <- Zv[,1]
y <- Zv[,2]
#y <- jitter(Vp[,2], factor=1, amount=max(y)-min(y))
plot(x, y, type="n")
text(x, y, labels=unique_words, cex=0.6)

Note the axes on the first dimension is relatively close, rotating about the other axis provides additional view of the data (often known as a grand tour).

par.old <- par(mfrow=c(2,2))
x <- Zv[,2]
for(i in 3:6) {
  y <- Zv[,i]
  plot(x, y, type="n")
  text(x, y, labels=unique_words, cex=0.6)
}
par(par.old)

Note we can explore the space of one projection to examine the terms that are located in the ordination. Given that the area between -0.1 and 0.1 is quite dense it is possible to explore this further. However interactive graphics of course would allow much more detailed exploration of the terms in the vector space.

Hence we can investigate the use of the library “scatterD3” to support the visualisation. Here we are using the 60th dimension for “allowance” and the 88th dimension for “apply”. The visualisation that is produced visualises the terms in relation to the axes formed in the search space by the terms “allowance” and “apply”.

require(scatterD3)
x <- Zv[,60]
y <- Zv[,88]
plotData <- data.frame(x=x, y=y,labels=unique_words[1:length(x)])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0)

While the resulting ordination is tightly clustered around the center, those terms around the perimeter do provide some indication as to topics that are identifiable outside of the central cluster.

In this visualisation we note that the axis have opposing directions for “claimable” and “reported” for example.

We can also investigate in higher dimensions although this is somewhat unwieldy.

x <- Zv[,1]
y <- Zv[,60]
z <- Zv[,20]
require(threejs)
terms <- unique_words[1:length(x)]
scatterplot3js(x,y,z, pch=terms, size=0.5, font.symbols="size:10pt;", axis=TRUE)

We can also investigate a multidimensional scaling technique focusing only on the terms rather than documents using a euclidean distance between terms in the projection if we consider the matrix \(V'\) to represent the relationship between terms we then make use of the vectors of each row of the matrix to calculate pairwise distances between each object (or term).

The MDS method will generate an approximate distance matrix in less dimensions using an iterative approach. We will first generate a metric MDS (which should yield results similar to the eigendecomposition) and then we will use a non-metric approach.

For example, we first examine the metric MDS and applying the distance on the transpose of the document term matrix rather than the term dimensions.

T <- scale(as.matrix(doc_mat[,2:ncol(doc_mat)]))
T <- t(T)
Tdist <- dist(T, method="euclidean")
Tmds <- cmdscale(Tdist, k=3, eig=TRUE)
Tmds$GOF
[1] 0.01172264 0.01172264

And plot the resulting ordination, in this case, we have generated an MDS for 3 dimensions, which is a reduction in scale, and the visualisation is able to present the terms along either of the 3 dimensions.

x <- Tmds$points[,1]
y <- Tmds$points[,2]
y2 <- Tmds$points[,3]
plotData <- data.frame(x=x, y=y, y2=y2, labels=unique_words[1:length(x)])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0)

The resulting ordination is still quite dense and note that it is very similar to the components produced form the SVD, this is because the metric MDS makes use of an eigenvalue solution to determine the best fit approximation for the distance matrix. Hence the first 2 dimensions visualised are very similar to those generated from the SVD. We can also revisit the 2nd and third dimensions.

scatterD3(plotData, x=y, y=y2, lab=labels, point_opacity=0)

This ordination at the default zoom contains the following quadrants.

We can also experiment with the nonmetric MDS which approximates the original distance matrix by relative ranking rather than by eigenvalue solutions.

require(vegan)
Loading required package: vegan
Loading required package: permute

Attaching package: ‘permute’

The following object is masked from ‘package:igraph’:

    permute

Loading required package: lattice
This is vegan 2.4-3

Attaching package: ‘vegan’

The following object is masked from ‘package:igraph’:

    diversity
Tmds2 <- monoMDS(Tdist, k=3)
x <- Tmds2$points[,1]
y <- Tmds2$points[,2]
z <- Tmds2$points[,3]
plotData <- data.frame(x=x, y=y,labels=unique_words[1:length(x)])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0)

The projection of terms appears to be much less similar to the metric MDS and the SVD. The method approximates the order of distances only. For the purposes of the analysis, the components resulting from the SVD and metric MDS will be used.

#scatterplot3js(x,y,z, pch=terms, size=0.5, font.symbols="size:10pt;", axis=TRUE)

Term Frequency

It would be useful to make use of additional metrics in order to assist with the visualisation of terms, to do this we can make use of some kind of weighting for the term. A simplistic measure such as the inverse document frequency may be used to weight individual terms, this weighting can be used to alter the size of the terms.

term_freq <- load_term_counts(outputBase)
temp1 <- term_freq[order(-term_freq$count),]

Applying the count to the ordination.

x <- Tmds$points[,1]
y <- Tmds$points[,2]
plotData <- data.frame(x=x, y=y, y2=y2, labels=unique_words[1:length(x)],
                       size=term_freq$count[1:length(x)])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0.2, size_var=size)

Clustering

Clustering methods can be performed to visualise potential groups of terms and additionally utterances.

Terms can be clustered using either the distance matrix or the search space \(V_s\) from the SVD, the agglomerative clustering method can be used to visualise the term clustering using a dendogram, and generate cuts based on a desired estimate of distance between groups.

First we investigate the clusters based on the distance matrix for the terms.

hc1 <- hclust(Tdist, method="centroid")
plot(hc1, hang=-1, cex=0.3)

It is difficult to interpret the dendogram without saving the output to PDF and zooming into the terms, however visually we can intuitively select the number of appropriate clusters, the areas where the lines are high and densely packed can be used as a subjective guideline to determining the appropriate number. Based on the diagram we can select approximately 20 clusters for exploration.

cuts <- cutree(hc1, k=20)
hist(cuts, breaks=20)

x <- Tmds$points[,1]
y <- Tmds$points[,2]
y2 <- Tmds$points[,3]
plotData <- data.frame(x=x, y=y, y2=y2, labels=unique_words[1:length(x)], cluster=factor(cuts[1:length(x)]),
                       size=term_freq$count[1:length(x)])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0.2, col_var=cluster, legend_width=0,
          size_var=size)

From the histogram of the cuts we can see the larger populations are within the first few clusters.

It is also possible to explore the use of the clustering method on the search space \(V_s\), the issue however is that the Vs matrix contains both positive and negative values. It is preferable to normalise the Vs matrix within a value of 0 and 1 instead.

D <- t(apply(Vp, 1, function(x)(x-min(x))/(max(x)-min(x))))
Ds <- as.dist(D)
hc2 <- hclust(Ds, method="centroid")
plot(hc2, hang=-1, cex=0.3)

The resulting dendogram is much harder to separate, we can attempt to use the intuition from the previous clustering in order to choose approximately 20 clusters and plot the search space. This is visualised in the 3rd and 4th dimensions of the search space.

cuts2 <- cutree(hc2, k=20)
hist(cuts2, breaks=20)

plotData <- data.frame(x=Vs[,3], y=Vs[,4],labels=unique_words[1:length(Vs[,2])], cluster=cuts2[1:length(Vs[,2])], size=term_freq$count[1:length(Vs[,2])])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0.2, col_var=cluster, legend_width=0
          ,size_var=size)
NA values in size_var. Values set to min(0, size_var)

From the histogram of the cuts we can see this method does not produce good separation of clusters as the MDS method. We can instead investigate the kmeans method of clustering first estimating a division of roughly 20 clusters.

k <- kmeans(Vs, 20)
plotData <- data.frame(x=Vs[,3], y=Vs[,4],labels=unique_words[1:length(Vs[,2])], cluster=k$cluster, size=term_freq$count[1:length(Vs[,2])])
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0.2, col_var=cluster, 
          legend_width=0, size_var=size)
NA values in size_var. Values set to min(0, size_var)

This gives some separation that is more apparent when zooming into the clusters the coloration gives some details as to the separation between groups of terms.

Similarly we may be able to review the clusters for documents instead of terms. The document matrix may be more divisible due to the separation based on tags. We plot the document search space and the identifiers for each document.

cnts <- count(data, group_by=factor(app_tag))
k <- nrow(cnts)
Us <- searchU%*%Ss
clust <- kmeans(Us, k)
plotData <- data.frame(x=Us[,2], y=Us[,3], labels=doc_mat[,1], cluster=clust$cluster)
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0, legend_width=0, col_var=cluster)

The above plot suggests that there may be some separability based on clustering the decomposition of documents. Although does not indicate which is common amongst the documents. Applying the same data but using the app_tag as a label produces a different representation.

plotData <- data.frame(x=Us[,2], y=Us[,3], labels=doc_mat[,1], cluster=clust$cluster, tag=data$app_tag)
scatterD3(plotData, x=x, y=y, lab=labels, point_opacity=0, legend_width=0, col_var=tag)

The coloration produced by app tags is visually much less segments in the data, this suggests that the manually assigned labels are much more artificial than those clusters arising from the document ordination. It is possible to test this by attempting to fit classifiers to the document set. Training separate classifiers to label first by app tag and second by cluster.

The next experiment will perform a comparison in training classifiers first on the app_tag, and secondly on clusters generated for the documents. This comparison will then consider the differences in the segmentation of the data. The difficulty of using clusters for classification is in the process of labelling the clusters. In order to do so, further manual inspection of each of the documents in each cluster is required. For example, inspecting the counts in each cluster.

data$cluster <- clust$cluster
cnts <- count(data, group_by=cluster)
cnts[order(-cnts$n),]
hist(cnts$n, breaks=nrow(cnts))

And choosing a cluster with a high enough count to inspect.

temp <- data[order(data$cluster),]
data.frame(transcription=temp[temp$cluster == 14,]$transcription)

Suggests this particular cluster is concerned with youth allowance payments, changing circumstances, changes to appointments and health care cards.

Inspecting the next largest cluster.

temp <- data[order(data$cluster),]
data.frame(transcription=temp[temp$cluster == 36,]$transcription)

Seems to be related to enquiries, seems to be around reporting earnings, speaking to an operator, enquiring about claims status, rejection of claims and cancellations.

Given the number of small sized clusters it may be useful to recluster the documents at approximately 34 clusters rather than the full 103 (103 from the number of original tags).

idx <- which(cnts$n >= 100)
smallCnt <- cnts[-idx,]
nrow(smallCnt)
[1] 80
nrow(cnts[idx,])
[1] 23

In the next notebook, we will revisit the clustering, and use a smaller number of clusters in order to perform classification. This notebook has provided some examples of exploring the term space using ordination, and provided a brief example of document clustering. Note however that clustering is a subjective process, there are some guideline metrics such as MSE within clusters that can be used to give a subjective measure of cluster density, and some rough guidelines around cuts in dendograms, however there is still a large amount of manual work involved in hand labelling the resulting clusters. The hand labelling can be assisted a great deal by visualisation and scripting capabilities that allow rapid exploration and relabelling of clusters. This document provided an example of how this can be done in R.

LS0tCnRpdGxlOiAiRXhhbXBsZSBUZXh0IE9yZGluYXRpb24sIENsdXN0ZXJpbmcgYW5kIENsYXNzaWZpY2F0aW9uIgphdXRob3I6ICJDaHJpcyBEYXZleSIKZGF0ZTogJzA0LTA2LTIwMTcnCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIEludHJvZHVjdGlvbgoKVGhpcyBkb2N1bWVudCBwcm92aWRlcyBhIGRlbW9uc3RyYXRpb24gb2YgY29udmVyc2lvbiBvZiB0ZXh0IGludG8gYSBkb2N1bWVudCBtYXRyaXgsIGFuZCB0aGUgYXBwbGljYXRpb24gb2YgYQpudW1iZXIgb2YgdGVjaG5pcXVlcyBvbiB0aGUgcmVzdWx0aW5nIG1hdHJpeC4KCgpfX1JlYWRpbmcgdGhlIHJhdyBkYXRhX18KCkluIHRoaXMgZXhhbXBsZSB0aGUgZGF0YSBoYXMgdGhlIGNzdiBmb3JtYXQgd2l0aCBoZWFkaW5ncwoKLSByb3cKICAtIGEgcm93IGlkCiAgCi0gdHJhbnNjcmlwdGlvbgogIC0gdGhlIHRleHQgdHJhbnNjcmlwdGlvbiBmb3IgYSBnaXZlbiB1dHRlcmFuY2UKICAKLSBvY2N1cnJlbmNlCiAgLSB0aGUgbnVtYmVyIG9mIG9jY3VyYW5jZXMgb2YgdGhlIHV0dGVyYW5jZQogIAotIGRvCiAgLSBhIHZlcmIgYXNzb2NpYXRlZCB3aXRoIHRoZSB1dHRlcmFuY2UuCiAgCi0gd2hhdAogIC0gYSBub3VuIGFzc29jaWF0ZWQgd2l0aCB0aGUgdXR0ZXJhbmNlCiAgCi0gc2VtX3RhZwogIC0gYSBoYW5kIGxhYmVsbGVkIHNlbWFudGljIHRhZyBhcHBsaWVkIHRvIHRoZSB1dHRlcmFuY2UuCiAgCi0gYXBwX3RhZwogIC0gYSBoYW5kIGxhYmVsbGVkIHRhZyB0aGF0IGlzIGFzc29jaWF0ZWQgd2l0aCB0aGUgdXR0ZXJhbmNlLgogIAogIApJbiByZWFkaW5nIHRoZSBkYXRhIHdlIHdpbGwgYXNzb2NpYXRlIHRoZSByb3cgaWQsIGFuZCB3aWxsIHJldGFpbiB0aGUgYXBwX3RhZy4KVGhlIHRyYW5zY3JpcHRpb24gd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIHRmLWlkZiBtYXRyaXggdXNpbmcgYSBzaW1wbGUgdG9rZW5pc2F0aW9uLiBUaGUgdGYtaWRmIGlzIGNhbGN1bGF0ZWQgbGFyZ2VseSBmcm9tIHRoZSBjb21wb25lbnRzIG9mIAoKX19UZXJtIEZyZXF1ZW5jeV9fCgokJAp0Zih0LGQpID0gMC41ICsgMC41XHRpbWVzXGZyYWN7Zl97dCxkfX17bWF4KGZfe3QnLGR9OnQnXGluIGQpfQokJApGb3IgdGVybSAkdCQgaW4gZ2l2ZW4gZG9jdW1lbnQgJGQkIHRvIHByb2R1Y2UgZnJlcXVlbmN5IGZvciB0aGUgZG9jdW1lbnQgJGZfe3QsZH0kLgoKX19JbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeV9fCiQkCmlkZih0LEQpID0gXGxvZ1xsZWZ0KCBcZnJhY3tOfXt8ZCBcaW4gRDogdCBcaW4gZHx9XHJpZ2h0KQokJAoKd2l0aCAkTiQgYmVpbmcgdGhlIHRvdGFsIG51bWJlciBvZiBkb2N1bWVudHMsIHRoYXQgY29udGFpbiBlYWNoIHRlcm0gJHQkLgoKX19UZXJtIEZyZXF1ZW5jeSBJbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeV9fCgokJAp0ZmlkZih0LGQpID0gdGYodCxkKVx0aW1lcyBpZGYodCxEKQokJAoKVGhlIFRGLUlERiBtYXRyaXggaXMgdGhlbiB1c2VkIGFzIHRoZSBpbnB1dCB0byB0aGUgcHJvY2VkdXJlLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZXF1aXJlKGRwbHlyKQpzb3VyY2UoInByZXByb2Nlc3NfdGV4dC5SIikKc291cmNlKCJwcmVwYXJlX3N2ZF9kb2N1bWVudF9tb2RlbC5SIikKc291cmNlKCJzdmRfc2VhcmNoLlIiKQoKZmlsZSA8LSAiaW5wdXQvY3NleGFtcGxlL3RhZ2dpbmdfZGF0YS5jc3YiCm91dHB1dEJhc2UgPC0gImRhdGEvY3NleGFtcGxlLyIgIAoKZGF0YV9jb2xzIDwtIGMoInJvdyIsICJ0cmFuc2NyaXB0aW9uIiwgIm9jY3VycmVuY2UiLCAiZG8iLCAid2hhdCIsICJzZW1fdGFnIiwgImFwcF90YWciKQppZF9jb2xzIDwtIGMoInJvdyIpCgojIG5vdGUgdGhpcyBpcyB1c2VkIHRvIGdlbmVyYXRlIHRoZSBkb2N1bWVudCB0ZXJtIG1hdHJpeCBmcm9tIHNjcmF0Y2ggYW5kIGlzIGNvbW1lbnRlZCBvdXQKIyBmb3Igc3Vic2VxdWVudCBydW5zIGFmdGVyIGluaXRpYWxseSB1c2VkIHRvIGJ1aWxkIHRoZSBkb2N1bWVudCB0ZXJtIG1hdHJpeC4KI3ByZXBhcmVfZG9jdW1lbnRfbW9kZWwoZmlsZSwgZGF0YV9jb2xzLCBpZF9jb2xzLCBvdXRwdXRCYXNlLCB0ZXh0X2NvbD0idHJhbnNjcmlwdGlvbiIpCgpgYGAKCkFmdGVyIGdlbmVyYXRpbmcgdGhlIG9yaWdpbmFsIG1vZGVsIHdlIHJlYWQgaXQgYmFjayBpbiBhbmQgdXNlIGl0IHRvIGV4cGxvcmUgdGhlIGRhdGEuCgpgYGB7cn0KZGF0YSA8LSBsb2FkX2RvY3VtZW50X2RhdGEob3V0cHV0QmFzZSkKZG9jX21hdCA8LSBsb2FkX2RvY3VtZW50X3Rlcm1fbWF0KG91dHB1dEJhc2UpCiAgdW5pcXVlX3dvcmRzIDwtIGxvYWRfdW5pcXVlX3dvcmRzKG91dHB1dEJhc2UpCiAgc2VhcmNoQSA8LSBsb2FkX3NlYXJjaF9zcGFjZShvdXRwdXRCYXNlKQogIAogIHNlYXJjaFUgPC0gc2VhcmNoQSR1CiAgc2VhcmNoUyA8LSBzZWFyY2hBJGQKICAjIHRyYW5zcG9zZWQgcmVsYXRpb24gb2YgYXR0cmlidXRlcyB0byBvYmplY3RzIHJvd3MgZXF1YWwgdGhlIGF0dHJpYnV0ZXMKICBzZWFyY2hWIDwtIHNlYXJjaEEkdgoKYGBgCgpUaGUgc3lzdGVtIHRoYXQgaXMgZ2VuZXJhdGVkIGlzIGEgbWF0cml4IGRlY29tcG9zaXRpb24gcHJvZHVjZWQgYnkgYW4gU1ZELiBUaGlzIGlzIGRlc2NyaWJlZCBhcyBhIGRlY29tcG9zaXRpb24gb2YgdGhlICRtIFx0aW1lcyBuJCBkb2N1bWVudC10ZXJtIG1hdHJpeCAkQSQgc3VjaCB0aGF0CgokJApBID0gVVNWJwokJAoKV2hlcmUKCi0gJFUkIHdpbGwgYmUgYSBtYXRyaXggb2YgdGhlIGRpbWVuc2lvbiAkbSBcdGltZXMgayQgcmVwcmVzZW50aW5nIHRoZSBlaWdlbnZlY3RvcnMgZm9yIHRoZSByb3dzIG9mIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggJEFBJyQgVGhlIGNvbHVtbnMgaW4gJFUkIGZvcm0gYW4gb3J0aG9ub3JtYWwgYmFzaXMuCgotICRTJCB3aWxsIGJlIGEgdmVjdG9yIG9mIGRpbWVuc2lvbiAkayQgcmVwcmVzZW50aW5nIHRoZSBlaWdlbnZhbHVlcyBmb3IgYm90aCBjb3JyZWxhdGlvbiBtYXRyaWNlcyAkQUEnJCBhbmQgJEEnQSQuCgotICRWJyQgaXMgdGhlIG1hdHJpeCBvZiBkaW1lbnNpb24gJGsgXHRpbWVzIG4kIHJlcHJlc2VudGluZyB0aGUgZWl2ZW52ZWN0b3JzIG9mIHRoZSBjb2x1bW5zIG9mIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggJEEnQSQuIFRoZSBjb2x1bW5zIGluICRWJCBhbHNvIGZvcm0gYW4gb3J0aG9ub3JtYWwgYmFzaXMuCgpOb3RlIHRoYXQgdGhlIG1hdHJpeCAkQSQgaXMgc3RhbmRhcmRpc2VkIHByaW9yIHRvIHBlcmZvcm1pbmcgdGhlIGRlY29tcG9zaXRpb24uCgpUaGUgJFMkIG1hdHJpeCBpcyBhbHNvIGxhdGVyIGNvbnZlcnRlZCBpbnRvIGEgZGlhZ29uYWwgbWF0cml4ICRcU2lnbWEgPSBJU157MS8yfSQgd2hpY2ggaXMgdXNlZCBpbiBwcm9qZWN0aW5nIG1hdHJpY2VzIGludG8gYSBzZWFyY2ggc3BhY2UuCgpUaGVyZSBhcmUgYSBudW1iZXIgb2YgcmVwcmVzZW50YXRpb25zIGluIHRoZSBsaW5lYXIgc3BhY2UgdGhhdCByZXN1bHRzIHRoYXQgbWF5IGJlIHVzZWZ1bC4KCk5vdGUgYWxzbyB0aGF0ICRTJCBjYW4gYmUgYXBwcm94aW1hdGVkIGFzLgoKJCQKUyA9IFUnIEEgVgokJAoKClRoZSB0b3RhbCB2YXJpYXRpb24gZXhwbGFpbmVkIGJ5IGVhY2ggY29tcG9uZW50IG9mIHRoZSBkZWNvbXBvc2l0aW9uIGNhbiBiZSBkZXRlcm1pbmVkIGJ5IGludGVycHJldGluZyAkUyQgYXMgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBhbmQgc3F1YXJpbmcgaXQgdG8gZGV0ZXJtaW5lIHRoZSB2YXJpYW5jZSwgdGhlIHByb3BvcnRpb24gb2YgdmFyaWFuY2UgY2FuIGJlIGRlZmluZWQgYXMKCiQkCmZfayA9IFxmcmFje3Nfe2t9XjJ9e1xzdW1fe2k9MX1eciBzX2leMn0KJCQKClRoZSBtZWFzdXJlIG9mIHRoZSB0b3RhbCBlbnRyb3B5IChvciBhbW91bnQgb2YgZGlzb3JkZXIgaW4gYSBzZXQgb2Ygb2JqZWN0cykgaW4gdGhlIGRlc2lnbiBtYXRyaXggJEEkIGNhbiBiZSBjYWxjdWxhdGVkIGFzCgokJAplbnRyb3B5ID0gXGZyYWN7LTF9e1xsb2cgcn0gXHN1bV97az0xfV5yIFxsb2cgKGZfaykKJCQKCkEgc2NyZWUgcGxvdCBjYW4gYmUgdXNlZCB0byB2aXN1YWxpc2UgdGhlIGNvbnRyaWJ1dGlvbiAkZl9rJCB0byB0b3RhbCB2YXJpYXRpb24gaW4gdGhlIGRhdGEgc2V0IGZvciBlYWNoIGNvbXBvbmVudC4KCmBgYHtyfQp0b3RhbCA8LSBzdW0oc2VhcmNoU14yKQplaWdlblYgPC0gc2VhcmNoU14yCgpwbG90KGVpZ2VuViwgdHlwZT0iYiIpCgpwbG90KGVpZ2VuVi90b3RhbCwgdHlwZT0iYiIpCgoKYGBgCgoKVGhlIGZpcnN0IGNvbXBvbmVudCBleHBsYWlucyBjbG9zZSB0byAxMDAlIG9mIGFsbCB2YXJpYXRpb24sIHRoZSBjb3JyZXNwb25kaW5nIGVpZ2VudmVjdG9ycyBjYW4gYmUgY29uc2lkZXJlZCBhcyBiZWluZyBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBhbGwgdGVybXMsIG5vdGUgdGhhdCBkdWUgdG8gdGhlIGhpZ2ggYW1vdW50IG9mIHZhcmlhdGlvbiBleHBsYWluZWQsIGl0IGlzIGRpZmZpY3VsdCB0byBkZXRlcm1pbmUgdGhlIGNvbnRyaWJ1dGlvbiBvZiB0aGUgb3RoZXIgY29tcG9uZW50cyBmcm9tIHRoZSBzZWNvbmQgY29tcG9uZW50IG9ud2FyZHMuIAoKV2UgY2FuIGVpdGhlciBlc3RpbWF0ZSB0aGlzIGFzIGEgcHJvcG9ydGlvbiBvZiB0aGUgcmVtYWluaW5nIGZyYWN0aW9uIG9yIHBsb3QgdGhlIHByb3BvcnRpb24gb2YgdGhlIHRvdGFsIHZhcmlhdGlvbi4KClRoZSBzZWNvbmQgY29tcG9uZW50cyBvbndhcmRzIGFyZSBwbG90dGVkIGFzIGZvbGxvd3MuCgpgYGB7cn0KCnBsb3QoZWlnZW5WWzI6MTAwXSwgdHlwZT0iYiIpCgpwbG90KGVpZ2VuVlsyOjEwMF0vdG90YWwsIHR5cGU9ImIiKQpgYGAKSWYgd2Ugd2VyZSBsb29raW5nIGZvciB0aGUgdGVjaG5pcXVlIHRvIHlpZWxkIGEgaGlnaCBhbW91bnQgb2YgY29tcHJlc3Npb24gdGhpcyBzdWdnZXN0cyB0aGF0IHRoZXJlIGlzIGxpdHRsZSBtb3JlIHRoYW4gdGhlIGZpcnN0IGZldyBsaW5lYXIgY29tYmluYXRpb25zIHdvdWxkIGJlIHJlcXVpcmVkIGZyb20gdGhlIHByb2plY3Rpb24gb2YgJEFVJCBvciAkQVYnJCB0byBlZmZlY3RpdmVseSBzdW1tYXJpc2UgdGhlIGRhdGEsIGFuZCBpbiBmYWN0IHdlIHdpbGwgdXNlIHRoaXMgY29tcHJlc3Npb24gdGVjaG5pcXVlIGxhdGVyIG9uIGR1cmluZyBjbGFzc2lmaWNhdGlvbi4KCldoZXJlIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBpcyByZXF1aXJlZCwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgdGhlIHN1YnNldHMgJGskIHRvIGNvbXB1dGUgcmVkdWNlZCBjb3JyZWxhdGlvbiBtYXRyaWNlcywgdGhhdCBhcmUgb2YgbGVzcyBkaW1lbnNpb24gdGhhbiAkQUEnJCBhbmQgJEEnQSQgdGhlc2UgYXJlIHRoZSB0cnVuY2F0ZWQgY29ycmVsYXRpb24gbWF0cmljZXMgYW5kIGNhbiBiZSBjYWxjdWxhdGVkIGFzOgoKJCQKQV9rIEFfaycgPSBVX2sgKFNfa14yKSBVX2snCiQkCgpzaW1pbGFybHkKCgokJApBX2snQV9rID0gVl9rIFNfa14yIFZfaycKJCQKClRoZSByZXN1bHRpbmcgY29ycmVsYXRpb24gbWF0cmljZXMgYXJlIGxvd2VyIGRpbWVuc2lvbmFsIHJlcHJlc2VudGF0aW9ucyBvZiB0aGUgb3JpZ2luYWwgY29ycmVsYXRpb24gbWF0cml4LiBUaGVzZSBtYXkgYmUgdXNlZnVsIHdoZXJlIGxvd2VyIGRpbWVuc2lvbmFsIGFwcHJveGltYXRpb24gb2YgJEFfayQgaXMgdXNlZCBpbiBhIGRpc3RyaWJ1dGlvbiByZXF1aXJpbmcgY29ycmVsYXRpb24gbWF0cml4IGFzIGEgcGFyYW1ldGVyLgoKCk5vdGUgYWxzbyB0aGF0IHRoZSBzY3JlZSBwbG90IGRvZXMgbm90IHNob3cgdGhlIHRvdGFsIG51bWJlciBvZiBjb21wb25lbnRzIGFzIHRoZSByZW1haW5kZXIgYmVjb21lIGluY3JlYXNpbmdseSBzbWFsbCwgYW5kIHRoZXJlZm9yZSBpbnNpZ25pZmljYW50LgoKVGhlIHRvdGFsIGRpbWVuc2lvbnMgb2YgdGhlIHJhdyB0ZXJtIGRvY3VtZW50IG1hdHJpeCBpcyBhcyBmb2xsb3dzOgoKYGBge3J9CmRpbShkb2NfbWF0KQpgYGAKVGhhdCBpcyB3ZSBoYXZlIGEgcmVsYXRpdmVseSBzbWFsbCB2b2NhYnVsYXJ5IGluIHRoaXMgZGF0YSBzZXQsIHRoZXJlIGFyZSBvbmx5IDE2MDcgdGVybXMsIGhvd2V2ZXIgd2UgaGF2ZSBqdXN0IG92ZXIgMTEgdGhvdXNhbmQgdXR0ZXJhbmNlcyBpbiB0b3RhbC4KCldlIGNhbiB2aXN1YWxpc2UgdGhlIHRlcm0gdmVjdG9yIHNwYWNlIGFzIGFuIG9yZGluYXRpb24gdXNpbmcgdGhlIHByb2plY3Rpb24gb2YgdGhlIHRlcm1zIGludG8gdGhlIHZlY3RvciBzcGFjZSBhbmQgdGhlbiByZW5kZXJpbmcgdGhlIGZpcnN0IHR3byBjb21wb25lbnRzLiBXZSB3aWxsIHVzZSB0aGUgdmVjdG9yIHNwYWNlIHNjYWxlZCBieSB0aGUgc3F1YXJlIHJvb3Qgb2YgZWFjaCBzaW5ndWxhciB2YWx1ZSB3aGljaCBpcyB0aGUgc2VhcmNoIHNwYWNlIHRoYXQgaXMgdXNlZCB3aGVuIHByb2plY3RpbmcgdGVybSB2ZWN0b3JzIGludG8gdGhlIHRlcm0gc3BhY2UsIG5vdGUgdGhhdCBhIHNpbWlsYXIgc2VhcmNoIHNwYWNlIGlzIHVzZWQgd2hlbiBwZXJmb3JtaW5nIHF1ZXJpZXMgYWdhaW5zdCB0aGUgdGVybSBtYXRyaXguIFRoZSBzZWFyY2ggc3BhY2UgaXMgZm9ybWVkIGJ5IHRoZSBkaWFnb25hbCBtYXRyaXggJFNfcyA9IElTXnsxLzJ9JCAodGhpcyBpcyBhbHNvIGRlbm90ZWQgJFxTaWdtYSQgaW4gYXJ0aWNsZXMgb24gTFNJKSBhbmQgdGhlIHByb2R1Y3Qgb2YgdGhlIHNlYXJjaCBzcGFjZSBmb3IgdGhlIHRlcm1zICRWX3MgPSBWU19zJC4KCgpgYGB7cn0KbiA8LSBsZW5ndGgoc2VhcmNoUykKdGVtcCA8LSByZXAoMCwgbipuKQpTcyA8LSBtYXRyaXgodGVtcCwgbnJvdz1uLCBuY29sPW4pCmRpYWcoU3MpIDwtIHNxcnQoc2VhcmNoUykKClZwIDwtIChhcy5tYXRyaXgoc2VhcmNoVikpClZzIDwtIChWcCUqJVNzKQoKeCA8LSBWc1ssMV0KeSA8LSBWc1ssMl0KCiMgd2hhdCBpZiB3ZSBwcm9qZWN0IHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggWCdYIGZvciB0ZXJtcyBpbnRvIHRoZSBlaWdlbnZlY3RvcnMgVnAKWCA8LSBhcy5tYXRyaXgoZG9jX21hdCkKWCA8LSBzY2FsZShYKQpDIDwtIHQoWCklKiVYClp2IDwtIEMlKiVWcwpkaW0oWnYpCgp4IDwtIFp2WywxXQp5IDwtIFp2WywyXQoKI3kgPC0gaml0dGVyKFZwWywyXSwgZmFjdG9yPTEsIGFtb3VudD1tYXgoeSktbWluKHkpKQpwbG90KHgsIHksIHR5cGU9Im4iKQp0ZXh0KHgsIHksIGxhYmVscz11bmlxdWVfd29yZHMsIGNleD0wLjYpCmBgYAoKCk5vdGUgdGhlIGF4ZXMgb24gdGhlIGZpcnN0IGRpbWVuc2lvbiBpcyByZWxhdGl2ZWx5IGNsb3NlLCByb3RhdGluZyBhYm91dCB0aGUgb3RoZXIgYXhpcyBwcm92aWRlcyBhZGRpdGlvbmFsIHZpZXcgb2YgdGhlIGRhdGEgKG9mdGVuIGtub3duIGFzIGEgZ3JhbmQgdG91cikuCgpgYGB7ciwgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTB9CnBhci5vbGQgPC0gcGFyKG1mcm93PWMoMiwyKSkKeCA8LSBadlssMl0KZm9yKGkgaW4gMzo2KSB7CiAgeSA8LSBadlssaV0KICBwbG90KHgsIHksIHR5cGU9Im4iKQogIHRleHQoeCwgeSwgbGFiZWxzPXVuaXF1ZV93b3JkcywgY2V4PTAuNikKfQoKcGFyKHBhci5vbGQpCmBgYAoKTm90ZSB3ZSBjYW4gZXhwbG9yZSB0aGUgc3BhY2Ugb2Ygb25lIHByb2plY3Rpb24gdG8gZXhhbWluZSB0aGUgdGVybXMgdGhhdCBhcmUgbG9jYXRlZCBpbiB0aGUgb3JkaW5hdGlvbi4gR2l2ZW4gdGhhdCB0aGUgYXJlYSBiZXR3ZWVuIC0wLjEgYW5kIDAuMSBpcyBxdWl0ZSBkZW5zZSBpdCBpcyBwb3NzaWJsZSB0byBleHBsb3JlIHRoaXMgZnVydGhlci4gSG93ZXZlciBpbnRlcmFjdGl2ZSBncmFwaGljcyBvZiBjb3Vyc2Ugd291bGQgYWxsb3cgbXVjaCBtb3JlIGRldGFpbGVkIGV4cGxvcmF0aW9uIG9mIHRoZSB0ZXJtcyBpbiB0aGUgdmVjdG9yIHNwYWNlLgoKSGVuY2Ugd2UgY2FuIGludmVzdGlnYXRlIHRoZSB1c2Ugb2YgdGhlIGxpYnJhcnkgInNjYXR0ZXJEMyIgdG8gc3VwcG9ydCB0aGUgdmlzdWFsaXNhdGlvbi4gSGVyZSB3ZSBhcmUgdXNpbmcgdGhlIDYwdGggZGltZW5zaW9uIGZvciAiYWxsb3dhbmNlIiBhbmQgdGhlIDg4dGggZGltZW5zaW9uIGZvciAiYXBwbHkiLiBUaGUgdmlzdWFsaXNhdGlvbiB0aGF0IGlzIHByb2R1Y2VkIHZpc3VhbGlzZXMgdGhlIHRlcm1zIGluIHJlbGF0aW9uIHRvIHRoZSBheGVzIGZvcm1lZCBpbiB0aGUgc2VhcmNoIHNwYWNlIGJ5IHRoZSB0ZXJtcyAiYWxsb3dhbmNlIiBhbmQgImFwcGx5Ii4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVxdWlyZShzY2F0dGVyRDMpCnggPC0gWnZbLDYwXQp5IDwtIFp2Wyw4OF0KcGxvdERhdGEgPC0gZGF0YS5mcmFtZSh4PXgsIHk9eSxsYWJlbHM9dW5pcXVlX3dvcmRzWzE6bGVuZ3RoKHgpXSkKc2NhdHRlckQzKHBsb3REYXRhLCB4PXgsIHk9eSwgbGFiPWxhYmVscywgcG9pbnRfb3BhY2l0eT0wKQpgYGAKCgpXaGlsZSB0aGUgcmVzdWx0aW5nIG9yZGluYXRpb24gaXMgdGlnaHRseSBjbHVzdGVyZWQgYXJvdW5kIHRoZSBjZW50ZXIsIHRob3NlIHRlcm1zIGFyb3VuZCB0aGUgcGVyaW1ldGVyIGRvIHByb3ZpZGUgc29tZSBpbmRpY2F0aW9uIGFzIHRvIHRvcGljcyB0aGF0IGFyZSBpZGVudGlmaWFibGUgb3V0c2lkZSBvZiB0aGUgY2VudHJhbCBjbHVzdGVyLiAKCkluIHRoaXMgdmlzdWFsaXNhdGlvbiB3ZSBub3RlIHRoYXQgdGhlIGF4aXMgaGF2ZSBvcHBvc2luZyBkaXJlY3Rpb25zIGZvciAiY2xhaW1hYmxlIiBhbmQgInJlcG9ydGVkIiBmb3IgZXhhbXBsZS4KCldlIGNhbiBhbHNvIGludmVzdGlnYXRlIGluIGhpZ2hlciBkaW1lbnNpb25zIGFsdGhvdWdoIHRoaXMgaXMgc29tZXdoYXQgdW53aWVsZHkuCgpgYGB7cn0KeCA8LSBadlssMV0KeSA8LSBadlssNjBdCnogPC0gWnZbLDIwXQpyZXF1aXJlKHRocmVlanMpCnRlcm1zIDwtIHVuaXF1ZV93b3Jkc1sxOmxlbmd0aCh4KV0Kc2NhdHRlcnBsb3QzanMoeCx5LHosIHBjaD10ZXJtcywgc2l6ZT0wLjUsIGZvbnQuc3ltYm9scz0ic2l6ZToxMHB0OyIsIGF4aXM9VFJVRSkKYGBgCgpXZSBjYW4gYWxzbyBpbnZlc3RpZ2F0ZSBhIG11bHRpZGltZW5zaW9uYWwgc2NhbGluZyB0ZWNobmlxdWUgZm9jdXNpbmcgb25seSBvbiB0aGUgdGVybXMgcmF0aGVyIHRoYW4gZG9jdW1lbnRzIHVzaW5nIGEgZXVjbGlkZWFuIGRpc3RhbmNlIGJldHdlZW4gdGVybXMgaW4gdGhlIHByb2plY3Rpb24gaWYgd2UgY29uc2lkZXIgdGhlIG1hdHJpeCAkVickIHRvIHJlcHJlc2VudCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGVybXMgd2UgdGhlbiBtYWtlIHVzZSBvZiB0aGUgdmVjdG9ycyBvZiBlYWNoIHJvdyBvZiB0aGUgbWF0cml4IHRvIGNhbGN1bGF0ZSBwYWlyd2lzZSBkaXN0YW5jZXMgYmV0d2VlbiBlYWNoIG9iamVjdCAob3IgdGVybSkuCgoKVGhlIE1EUyBtZXRob2Qgd2lsbCBnZW5lcmF0ZSBhbiBhcHByb3hpbWF0ZSBkaXN0YW5jZSBtYXRyaXggaW4gbGVzcyBkaW1lbnNpb25zIHVzaW5nIGFuIGl0ZXJhdGl2ZSBhcHByb2FjaC4KV2Ugd2lsbCBmaXJzdCBnZW5lcmF0ZSBhIG1ldHJpYyBNRFMgKHdoaWNoIHNob3VsZCB5aWVsZCByZXN1bHRzIHNpbWlsYXIgdG8gdGhlIGVpZ2VuZGVjb21wb3NpdGlvbikgYW5kIHRoZW4gd2Ugd2lsbCB1c2UgYSBub24tbWV0cmljIGFwcHJvYWNoLgoKRm9yIGV4YW1wbGUsIHdlIGZpcnN0IGV4YW1pbmUgdGhlIG1ldHJpYyBNRFMgYW5kIGFwcGx5aW5nIHRoZSBkaXN0YW5jZSBvbiB0aGUgdHJhbnNwb3NlIG9mIHRoZSBkb2N1bWVudCB0ZXJtIG1hdHJpeCByYXRoZXIgdGhhbiB0aGUgdGVybSBkaW1lbnNpb25zLgoKCmBgYHtyfQpUIDwtIHNjYWxlKGFzLm1hdHJpeChkb2NfbWF0WywyOm5jb2woZG9jX21hdCldKSkKVCA8LSB0KFQpClRkaXN0IDwtIGRpc3QoVCwgbWV0aG9kPSJldWNsaWRlYW4iKQpUbWRzIDwtIGNtZHNjYWxlKFRkaXN0LCBrPTMsIGVpZz1UUlVFKQpUbWRzJEdPRgpgYGAKCkFuZCBwbG90IHRoZSByZXN1bHRpbmcgb3JkaW5hdGlvbiwgaW4gdGhpcyBjYXNlLCB3ZSBoYXZlIGdlbmVyYXRlZCBhbiBNRFMgZm9yIDMgZGltZW5zaW9ucywgd2hpY2ggaXMgYSByZWR1Y3Rpb24gaW4gc2NhbGUsIGFuZCB0aGUgdmlzdWFsaXNhdGlvbiBpcyBhYmxlIHRvIHByZXNlbnQgdGhlIHRlcm1zIGFsb25nIGVpdGhlciBvZiB0aGUgMyBkaW1lbnNpb25zLgoKYGBge3J9CnggPC0gVG1kcyRwb2ludHNbLDFdCnkgPC0gVG1kcyRwb2ludHNbLDJdCnkyIDwtIFRtZHMkcG9pbnRzWywzXQoKcGxvdERhdGEgPC0gZGF0YS5mcmFtZSh4PXgsIHk9eSwgeTI9eTIsIGxhYmVscz11bmlxdWVfd29yZHNbMTpsZW5ndGgoeCldKQpzY2F0dGVyRDMocGxvdERhdGEsIHg9eCwgeT15LCBsYWI9bGFiZWxzLCBwb2ludF9vcGFjaXR5PTApCgpgYGAKClRoZSByZXN1bHRpbmcgb3JkaW5hdGlvbiBpcyBzdGlsbCBxdWl0ZSBkZW5zZSBhbmQgbm90ZSB0aGF0IGl0IGlzIHZlcnkgc2ltaWxhciB0byB0aGUgY29tcG9uZW50cyBwcm9kdWNlZCBmb3JtIHRoZSBTVkQsIHRoaXMgaXMgYmVjYXVzZSB0aGUgbWV0cmljIE1EUyBtYWtlcyB1c2Ugb2YgYW4gZWlnZW52YWx1ZSBzb2x1dGlvbiB0byBkZXRlcm1pbmUgdGhlIGJlc3QgZml0IGFwcHJveGltYXRpb24gZm9yIHRoZSBkaXN0YW5jZSBtYXRyaXguIEhlbmNlIHRoZSBmaXJzdCAyIGRpbWVuc2lvbnMgdmlzdWFsaXNlZCBhcmUgdmVyeSBzaW1pbGFyIHRvIHRob3NlIGdlbmVyYXRlZCBmcm9tIHRoZSBTVkQuIFdlIGNhbiBhbHNvIHJldmlzaXQgdGhlIDJuZCBhbmQgdGhpcmQgZGltZW5zaW9ucy4KCmBgYHtyfQpzY2F0dGVyRDMocGxvdERhdGEsIHg9eSwgeT15MiwgbGFiPWxhYmVscywgcG9pbnRfb3BhY2l0eT0wKQpgYGAKClRoaXMgb3JkaW5hdGlvbiBhdCB0aGUgZGVmYXVsdCB6b29tIGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgcXVhZHJhbnRzLgoKLSB1cHBlciBsZWZ0ICJ5b3V0aCIsICJhbGxvd2FuY2UiLCAiY2xhaW0iCgotIGxvd2VyIGxlZnQgImNoYW5nZSIsICJyZXBvcnQiLCAiYXBwb2ludG1lbnQiLCAicGF5bWVudCIuCgotIHVwcGVyIHJpZ2h0LCAiYXBwbHkiLCAiYXBwbGljYXRpb24iIGFuZCAibG93IiwgImluY29tZSIgYW5kICJoZWFsdGgiICJjYXJlIiBhbmQgImhlYWx0aCIgImNhcmQiCgotIGxvd2VyIHJpZ2h0IGFwcGVhcnMgdG8gYmUgY2xlYXIgb2YgdGVybXMgaW4gdGhlIG9yZGluYXRpb24uCgpXZSBjYW4gYWxzbyBleHBlcmltZW50IHdpdGggdGhlIG5vbm1ldHJpYyBNRFMgd2hpY2ggYXBwcm94aW1hdGVzIHRoZSBvcmlnaW5hbCBkaXN0YW5jZSBtYXRyaXggYnkgcmVsYXRpdmUgcmFua2luZyByYXRoZXIgdGhhbiBieSBlaWdlbnZhbHVlIHNvbHV0aW9ucy4KCgoKYGBge3J9CnJlcXVpcmUodmVnYW4pClRtZHMyIDwtIG1vbm9NRFMoVGRpc3QsIGs9MykKeCA8LSBUbWRzMiRwb2ludHNbLDFdCnkgPC0gVG1kczIkcG9pbnRzWywyXQp6IDwtIFRtZHMyJHBvaW50c1ssM10KCnBsb3REYXRhIDwtIGRhdGEuZnJhbWUoeD14LCB5PXksbGFiZWxzPXVuaXF1ZV93b3Jkc1sxOmxlbmd0aCh4KV0pCnNjYXR0ZXJEMyhwbG90RGF0YSwgeD14LCB5PXksIGxhYj1sYWJlbHMsIHBvaW50X29wYWNpdHk9MCkKCmBgYAoKVGhlIHByb2plY3Rpb24gb2YgdGVybXMgYXBwZWFycyB0byBiZSBtdWNoIGxlc3Mgc2ltaWxhciB0byB0aGUgbWV0cmljIE1EUyBhbmQgdGhlIFNWRC4gVGhlIG1ldGhvZCBhcHByb3hpbWF0ZXMgdGhlIG9yZGVyIG9mIGRpc3RhbmNlcyBvbmx5LiBGb3IgdGhlIHB1cnBvc2VzIG9mIHRoZSBhbmFseXNpcywgdGhlIGNvbXBvbmVudHMgcmVzdWx0aW5nIGZyb20gdGhlIFNWRCBhbmQgbWV0cmljIE1EUyB3aWxsIGJlIHVzZWQuCgpgYGB7cn0KI3NjYXR0ZXJwbG90M2pzKHgseSx6LCBwY2g9dGVybXMsIHNpemU9MC41LCBmb250LnN5bWJvbHM9InNpemU6MTBwdDsiLCBheGlzPVRSVUUpCmBgYAoKX19UZXJtIEZyZXF1ZW5jeV9fCgpJdCB3b3VsZCBiZSB1c2VmdWwgdG8gbWFrZSB1c2Ugb2YgYWRkaXRpb25hbCBtZXRyaWNzIGluIG9yZGVyIHRvIGFzc2lzdCB3aXRoIHRoZSB2aXN1YWxpc2F0aW9uIG9mIHRlcm1zLCB0byBkbyB0aGlzIHdlIGNhbiBtYWtlIHVzZSBvZiBzb21lIGtpbmQgb2Ygd2VpZ2h0aW5nIGZvciB0aGUgdGVybS4gQSBzaW1wbGlzdGljIG1lYXN1cmUgc3VjaCBhcyB0aGUgaW52ZXJzZSBkb2N1bWVudCBmcmVxdWVuY3kgbWF5IGJlIHVzZWQgdG8gd2VpZ2h0IGluZGl2aWR1YWwgdGVybXMsIHRoaXMgd2VpZ2h0aW5nIGNhbiBiZSB1c2VkIHRvIGFsdGVyIHRoZSBzaXplIG9mIHRoZSB0ZXJtcy4KCmBgYHtyfQp0ZXJtX2ZyZXEgPC0gbG9hZF90ZXJtX2NvdW50cyhvdXRwdXRCYXNlKQp0ZW1wMSA8LSB0ZXJtX2ZyZXFbb3JkZXIoLXRlcm1fZnJlcSRjb3VudCksXQoKYGBgCgpBcHBseWluZyB0aGUgY291bnQgdG8gdGhlIG9yZGluYXRpb24uCgpgYGB7cn0KeCA8LSBUbWRzJHBvaW50c1ssMV0KeSA8LSBUbWRzJHBvaW50c1ssMl0KCnBsb3REYXRhIDwtIGRhdGEuZnJhbWUoeD14LCB5PXksIHkyPXkyLCBsYWJlbHM9dW5pcXVlX3dvcmRzWzE6bGVuZ3RoKHgpXSwKICAgICAgICAgICAgICAgICAgICAgICBzaXplPXRlcm1fZnJlcSRjb3VudFsxOmxlbmd0aCh4KV0pCgpzY2F0dGVyRDMocGxvdERhdGEsIHg9eCwgeT15LCBsYWI9bGFiZWxzLCBwb2ludF9vcGFjaXR5PTAuMiwgc2l6ZV92YXI9c2l6ZSkKCmBgYAoKX19DbHVzdGVyaW5nX18KCkNsdXN0ZXJpbmcgbWV0aG9kcyBjYW4gYmUgcGVyZm9ybWVkIHRvIHZpc3VhbGlzZSBwb3RlbnRpYWwgZ3JvdXBzIG9mIHRlcm1zIGFuZCBhZGRpdGlvbmFsbHkgdXR0ZXJhbmNlcy4KClRlcm1zIGNhbiBiZSBjbHVzdGVyZWQgdXNpbmcgZWl0aGVyIHRoZSBkaXN0YW5jZSBtYXRyaXggb3IgdGhlIHNlYXJjaCBzcGFjZSAkVl9zJCBmcm9tIHRoZSBTVkQsIHRoZSBhZ2dsb21lcmF0aXZlIGNsdXN0ZXJpbmcgbWV0aG9kIGNhbiBiZSB1c2VkIHRvIHZpc3VhbGlzZSB0aGUgdGVybSBjbHVzdGVyaW5nIHVzaW5nIGEgZGVuZG9ncmFtLCBhbmQgZ2VuZXJhdGUgY3V0cyBiYXNlZCBvbiBhIGRlc2lyZWQgZXN0aW1hdGUgb2YgZGlzdGFuY2UgYmV0d2VlbiBncm91cHMuCgpGaXJzdCB3ZSBpbnZlc3RpZ2F0ZSB0aGUgY2x1c3RlcnMgYmFzZWQgb24gdGhlIGRpc3RhbmNlIG1hdHJpeCBmb3IgdGhlIHRlcm1zLgoKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0xMH0KaGMxIDwtIGhjbHVzdChUZGlzdCwgbWV0aG9kPSJjZW50cm9pZCIpCnBsb3QoaGMxLCBoYW5nPS0xLCBjZXg9MC4zKQpgYGAKSXQgaXMgZGlmZmljdWx0IHRvIGludGVycHJldCB0aGUgZGVuZG9ncmFtIHdpdGhvdXQgc2F2aW5nIHRoZSBvdXRwdXQgdG8gUERGIGFuZCB6b29taW5nIGludG8gdGhlIHRlcm1zLCBob3dldmVyIHZpc3VhbGx5IHdlIGNhbiBpbnR1aXRpdmVseSBzZWxlY3QgdGhlIG51bWJlciBvZiBhcHByb3ByaWF0ZSBjbHVzdGVycywgdGhlIGFyZWFzIHdoZXJlIHRoZSBsaW5lcyBhcmUgaGlnaCBhbmQgZGVuc2VseSBwYWNrZWQgY2FuIGJlIHVzZWQgYXMgYSBzdWJqZWN0aXZlIGd1aWRlbGluZSB0byBkZXRlcm1pbmluZyB0aGUgYXBwcm9wcmlhdGUgbnVtYmVyLiBCYXNlZCBvbiB0aGUgZGlhZ3JhbSB3ZSBjYW4gc2VsZWN0IGFwcHJveGltYXRlbHkgMjAgY2x1c3RlcnMgZm9yIGV4cGxvcmF0aW9uLiAKCmBgYHtyfQpjdXRzIDwtIGN1dHJlZShoYzEsIGs9MjApCmhpc3QoY3V0cywgYnJlYWtzPTIwKQp4IDwtIFRtZHMkcG9pbnRzWywxXQp5IDwtIFRtZHMkcG9pbnRzWywyXQp5MiA8LSBUbWRzJHBvaW50c1ssM10KCnBsb3REYXRhIDwtIGRhdGEuZnJhbWUoeD14LCB5PXksIHkyPXkyLCBsYWJlbHM9dW5pcXVlX3dvcmRzWzE6bGVuZ3RoKHgpXSwgY2x1c3Rlcj1mYWN0b3IoY3V0c1sxOmxlbmd0aCh4KV0pLAogICAgICAgICAgICAgICAgICAgICAgIHNpemU9dGVybV9mcmVxJGNvdW50WzE6bGVuZ3RoKHgpXSkKCnNjYXR0ZXJEMyhwbG90RGF0YSwgeD14LCB5PXksIGxhYj1sYWJlbHMsIHBvaW50X29wYWNpdHk9MC4yLCBjb2xfdmFyPWNsdXN0ZXIsIGxlZ2VuZF93aWR0aD0wLAogICAgICAgICAgc2l6ZV92YXI9c2l6ZSkKCgoKYGBgCgpGcm9tIHRoZSBoaXN0b2dyYW0gb2YgdGhlIGN1dHMgd2UgY2FuIHNlZSB0aGUgbGFyZ2VyIHBvcHVsYXRpb25zIGFyZSB3aXRoaW4gdGhlIGZpcnN0IGZldyBjbHVzdGVycy4KCkl0IGlzIGFsc28gcG9zc2libGUgdG8gZXhwbG9yZSB0aGUgdXNlIG9mIHRoZSBjbHVzdGVyaW5nIG1ldGhvZCBvbiB0aGUgc2VhcmNoIHNwYWNlICRWX3MkLCB0aGUgaXNzdWUgaG93ZXZlciBpcyB0aGF0IHRoZSBWcyBtYXRyaXggY29udGFpbnMgYm90aCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgdmFsdWVzLiBJdCBpcyBwcmVmZXJhYmxlIHRvIG5vcm1hbGlzZSB0aGUgVnMgbWF0cml4IHdpdGhpbiBhIHZhbHVlIG9mIDAgYW5kIDEgaW5zdGVhZC4KCgoKCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MTB9CkQgPC0gdChhcHBseShWcCwgMSwgZnVuY3Rpb24oeCkoeC1taW4oeCkpLyhtYXgoeCktbWluKHgpKSkpCkRzIDwtIGFzLmRpc3QoRCkKaGMyIDwtIGhjbHVzdChEcywgbWV0aG9kPSJjZW50cm9pZCIpCnBsb3QoaGMyLCBoYW5nPS0xLCBjZXg9MC4zKQpgYGAKClRoZSByZXN1bHRpbmcgZGVuZG9ncmFtIGlzIG11Y2ggaGFyZGVyIHRvIHNlcGFyYXRlLCB3ZSBjYW4gYXR0ZW1wdCB0byB1c2UgdGhlIGludHVpdGlvbiBmcm9tIHRoZSBwcmV2aW91cyBjbHVzdGVyaW5nIGluIG9yZGVyIHRvIGNob29zZSBhcHByb3hpbWF0ZWx5IDIwIGNsdXN0ZXJzIGFuZCBwbG90IHRoZSBzZWFyY2ggc3BhY2UuIFRoaXMgaXMgdmlzdWFsaXNlZCBpbiB0aGUgM3JkIGFuZCA0dGggZGltZW5zaW9ucyBvZiB0aGUgc2VhcmNoIHNwYWNlLgoKYGBge3J9CmN1dHMyIDwtIGN1dHJlZShoYzIsIGs9MjApCmhpc3QoY3V0czIsIGJyZWFrcz0yMCkKcGxvdERhdGEgPC0gZGF0YS5mcmFtZSh4PVZzWywzXSwgeT1Wc1ssNF0sbGFiZWxzPXVuaXF1ZV93b3Jkc1sxOmxlbmd0aChWc1ssMl0pXSwgY2x1c3Rlcj1jdXRzMlsxOmxlbmd0aChWc1ssMl0pXSwgc2l6ZT10ZXJtX2ZyZXEkY291bnRbMTpsZW5ndGgoVnNbLDJdKV0pCnNjYXR0ZXJEMyhwbG90RGF0YSwgeD14LCB5PXksIGxhYj1sYWJlbHMsIHBvaW50X29wYWNpdHk9MC4yLCBjb2xfdmFyPWNsdXN0ZXIsIGxlZ2VuZF93aWR0aD0wCiAgICAgICAgICAsc2l6ZV92YXI9c2l6ZSkKYGBgCgpGcm9tIHRoZSBoaXN0b2dyYW0gb2YgdGhlIGN1dHMgd2UgY2FuIHNlZSB0aGlzIG1ldGhvZCBkb2VzIG5vdCBwcm9kdWNlIGdvb2Qgc2VwYXJhdGlvbiBvZiBjbHVzdGVycyBhcyB0aGUgTURTIG1ldGhvZC4gV2UgY2FuIGluc3RlYWQgaW52ZXN0aWdhdGUgdGhlIGttZWFucyBtZXRob2Qgb2YgY2x1c3RlcmluZyBmaXJzdCBlc3RpbWF0aW5nIGEgZGl2aXNpb24gb2Ygcm91Z2hseSAyMCBjbHVzdGVycy4KCmBgYHtyfQprIDwtIGttZWFucyhWcywgMjApCnBsb3REYXRhIDwtIGRhdGEuZnJhbWUoeD1Wc1ssM10sIHk9VnNbLDRdLGxhYmVscz11bmlxdWVfd29yZHNbMTpsZW5ndGgoVnNbLDJdKV0sIGNsdXN0ZXI9ayRjbHVzdGVyLCBzaXplPXRlcm1fZnJlcSRjb3VudFsxOmxlbmd0aChWc1ssMl0pXSkKc2NhdHRlckQzKHBsb3REYXRhLCB4PXgsIHk9eSwgbGFiPWxhYmVscywgcG9pbnRfb3BhY2l0eT0wLjIsIGNvbF92YXI9Y2x1c3RlciwgCiAgICAgICAgICBsZWdlbmRfd2lkdGg9MCwgc2l6ZV92YXI9c2l6ZSkKCmBgYAoKVGhpcyBnaXZlcyBzb21lIHNlcGFyYXRpb24gdGhhdCBpcyBtb3JlIGFwcGFyZW50IHdoZW4gem9vbWluZyBpbnRvIHRoZSBjbHVzdGVycyB0aGUgY29sb3JhdGlvbiBnaXZlcyBzb21lIGRldGFpbHMgYXMgdG8gdGhlIHNlcGFyYXRpb24gYmV0d2VlbiBncm91cHMgb2YgdGVybXMuCgpTaW1pbGFybHkgd2UgbWF5IGJlIGFibGUgdG8gcmV2aWV3IHRoZSBjbHVzdGVycyBmb3IgZG9jdW1lbnRzIGluc3RlYWQgb2YgdGVybXMuIFRoZSBkb2N1bWVudCBtYXRyaXggbWF5IGJlIG1vcmUgZGl2aXNpYmxlIGR1ZSB0byB0aGUgc2VwYXJhdGlvbiBiYXNlZCBvbiB0YWdzLiBXZSBwbG90IHRoZSBkb2N1bWVudCBzZWFyY2ggc3BhY2UgYW5kIHRoZSBpZGVudGlmaWVycyBmb3IgZWFjaCBkb2N1bWVudC4KCmBgYHtyfQpjbnRzIDwtIGNvdW50KGRhdGEsIGdyb3VwX2J5PWZhY3RvcihhcHBfdGFnKSkKayA8LSBucm93KGNudHMpClVzIDwtIHNlYXJjaFUlKiVTcwpjbHVzdCA8LSBrbWVhbnMoVXMsIGspCnBsb3REYXRhIDwtIGRhdGEuZnJhbWUoeD1Vc1ssMl0sIHk9VXNbLDNdLCBsYWJlbHM9ZG9jX21hdFssMV0sIGNsdXN0ZXI9Y2x1c3QkY2x1c3RlcikKc2NhdHRlckQzKHBsb3REYXRhLCB4PXgsIHk9eSwgbGFiPWxhYmVscywgcG9pbnRfb3BhY2l0eT0wLCBsZWdlbmRfd2lkdGg9MCwgY29sX3Zhcj1jbHVzdGVyKQpgYGAKVGhlIGFib3ZlIHBsb3Qgc3VnZ2VzdHMgdGhhdCB0aGVyZSBtYXkgYmUgc29tZSBzZXBhcmFiaWxpdHkgYmFzZWQgb24gY2x1c3RlcmluZyB0aGUgZGVjb21wb3NpdGlvbiBvZiBkb2N1bWVudHMuIEFsdGhvdWdoIGRvZXMgbm90IGluZGljYXRlIHdoaWNoIGlzIGNvbW1vbiBhbW9uZ3N0IHRoZSBkb2N1bWVudHMuIEFwcGx5aW5nIHRoZSBzYW1lIGRhdGEgYnV0IHVzaW5nIHRoZSBhcHBfdGFnIGFzIGEgbGFiZWwgcHJvZHVjZXMgYSBkaWZmZXJlbnQgcmVwcmVzZW50YXRpb24uCgpgYGB7cn0KcGxvdERhdGEgPC0gZGF0YS5mcmFtZSh4PVVzWywyXSwgeT1Vc1ssM10sIGxhYmVscz1kb2NfbWF0WywxXSwgY2x1c3Rlcj1jbHVzdCRjbHVzdGVyLCB0YWc9ZGF0YSRhcHBfdGFnKQpzY2F0dGVyRDMocGxvdERhdGEsIHg9eCwgeT15LCBsYWI9bGFiZWxzLCBwb2ludF9vcGFjaXR5PTAsIGxlZ2VuZF93aWR0aD0wLCBjb2xfdmFyPXRhZykKCmBgYApUaGUgY29sb3JhdGlvbiBwcm9kdWNlZCBieSBhcHAgdGFncyBpcyB2aXN1YWxseSBtdWNoIGxlc3Mgc2VnbWVudHMgaW4gdGhlIGRhdGEsIHRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgbWFudWFsbHkgYXNzaWduZWQgbGFiZWxzIGFyZSBtdWNoIG1vcmUgYXJ0aWZpY2lhbCB0aGFuIHRob3NlIGNsdXN0ZXJzIGFyaXNpbmcgZnJvbSB0aGUgZG9jdW1lbnQgb3JkaW5hdGlvbi4gSXQgaXMgcG9zc2libGUgdG8gdGVzdCB0aGlzIGJ5IGF0dGVtcHRpbmcgdG8gZml0IGNsYXNzaWZpZXJzIHRvIHRoZSBkb2N1bWVudCBzZXQuIFRyYWluaW5nIHNlcGFyYXRlIGNsYXNzaWZpZXJzIHRvIGxhYmVsIGZpcnN0IGJ5IGFwcCB0YWcgYW5kIHNlY29uZCBieSBjbHVzdGVyLiAKClRoZSBuZXh0IGV4cGVyaW1lbnQgd2lsbCBwZXJmb3JtIGEgY29tcGFyaXNvbiBpbiB0cmFpbmluZyBjbGFzc2lmaWVycyBmaXJzdCBvbiB0aGUgYXBwX3RhZywgYW5kIHNlY29uZGx5IG9uIGNsdXN0ZXJzIGdlbmVyYXRlZCBmb3IgdGhlIGRvY3VtZW50cy4gVGhpcyBjb21wYXJpc29uIHdpbGwgdGhlbiBjb25zaWRlciB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIHNlZ21lbnRhdGlvbiBvZiB0aGUgZGF0YS4KVGhlIGRpZmZpY3VsdHkgb2YgdXNpbmcgY2x1c3RlcnMgZm9yIGNsYXNzaWZpY2F0aW9uIGlzIGluIHRoZSBwcm9jZXNzIG9mIGxhYmVsbGluZyB0aGUgY2x1c3RlcnMuIEluIG9yZGVyIHRvIGRvIHNvLCBmdXJ0aGVyIG1hbnVhbCBpbnNwZWN0aW9uIG9mIGVhY2ggb2YgdGhlIGRvY3VtZW50cyBpbiBlYWNoIGNsdXN0ZXIgaXMgcmVxdWlyZWQuCkZvciBleGFtcGxlLCBpbnNwZWN0aW5nIHRoZSBjb3VudHMgaW4gZWFjaCBjbHVzdGVyLgoKYGBge3J9CmRhdGEkY2x1c3RlciA8LSBjbHVzdCRjbHVzdGVyCmNudHMgPC0gY291bnQoZGF0YSwgZ3JvdXBfYnk9Y2x1c3RlcikKY250c1tvcmRlcigtY250cyRuKSxdCmhpc3QoY250cyRuLCBicmVha3M9bnJvdyhjbnRzKSkKCgpgYGAKCkFuZCBjaG9vc2luZyBhIGNsdXN0ZXIgd2l0aCBhIGhpZ2ggZW5vdWdoIGNvdW50IHRvIGluc3BlY3QuCgpgYGB7cn0KCnRlbXAgPC0gZGF0YVtvcmRlcihkYXRhJGNsdXN0ZXIpLF0KZGF0YS5mcmFtZSh0cmFuc2NyaXB0aW9uPXRlbXBbdGVtcCRjbHVzdGVyID09IDE0LF0kdHJhbnNjcmlwdGlvbikKYGBgCgoKU3VnZ2VzdHMgdGhpcyBwYXJ0aWN1bGFyIGNsdXN0ZXIgaXMgY29uY2VybmVkIHdpdGggeW91dGggYWxsb3dhbmNlIHBheW1lbnRzLCBjaGFuZ2luZyBjaXJjdW1zdGFuY2VzLCBjaGFuZ2VzIHRvIGFwcG9pbnRtZW50cyBhbmQgaGVhbHRoIGNhcmUgY2FyZHMuCgpJbnNwZWN0aW5nIHRoZSBuZXh0IGxhcmdlc3QgY2x1c3Rlci4KCmBgYHtyfQp0ZW1wIDwtIGRhdGFbb3JkZXIoZGF0YSRjbHVzdGVyKSxdCmRhdGEuZnJhbWUodHJhbnNjcmlwdGlvbj10ZW1wW3RlbXAkY2x1c3RlciA9PSAzNixdJHRyYW5zY3JpcHRpb24pCmBgYAoKU2VlbXMgdG8gYmUgcmVsYXRlZCB0byBlbnF1aXJpZXMsIHNlZW1zIHRvIGJlIGFyb3VuZCByZXBvcnRpbmcgZWFybmluZ3MsIHNwZWFraW5nIHRvIGFuIG9wZXJhdG9yLCBlbnF1aXJpbmcgYWJvdXQgY2xhaW1zIHN0YXR1cywgcmVqZWN0aW9uIG9mIGNsYWltcyBhbmQgY2FuY2VsbGF0aW9ucy4KCkdpdmVuIHRoZSBudW1iZXIgb2Ygc21hbGwgc2l6ZWQgY2x1c3RlcnMgaXQgbWF5IGJlIHVzZWZ1bCB0byByZWNsdXN0ZXIgdGhlIGRvY3VtZW50cyBhdCBhcHByb3hpbWF0ZWx5IDM0IGNsdXN0ZXJzIHJhdGhlciB0aGFuIHRoZSBmdWxsIDEwMyAoMTAzIGZyb20gdGhlIG51bWJlciBvZiBvcmlnaW5hbCB0YWdzKS4KCmBgYHtyfQppZHggPC0gd2hpY2goY250cyRuID49IDEwMCkKc21hbGxDbnQgPC0gY250c1staWR4LF0KbnJvdyhzbWFsbENudCkKbnJvdyhjbnRzW2lkeCxdKQpgYGAKCkluIHRoZSBuZXh0IG5vdGVib29rLCB3ZSB3aWxsIHJldmlzaXQgdGhlIGNsdXN0ZXJpbmcsIGFuZCB1c2UgYSBzbWFsbGVyIG51bWJlciBvZiBjbHVzdGVycyBpbiBvcmRlciB0byBwZXJmb3JtIGNsYXNzaWZpY2F0aW9uLiBUaGlzIG5vdGVib29rIGhhcyBwcm92aWRlZCBzb21lIGV4YW1wbGVzIG9mIGV4cGxvcmluZyB0aGUgdGVybSBzcGFjZSB1c2luZyBvcmRpbmF0aW9uLCBhbmQgcHJvdmlkZWQgYSBicmllZiBleGFtcGxlIG9mIGRvY3VtZW50IGNsdXN0ZXJpbmcuCk5vdGUgaG93ZXZlciB0aGF0IGNsdXN0ZXJpbmcgaXMgYSBzdWJqZWN0aXZlIHByb2Nlc3MsIHRoZXJlIGFyZSBzb21lIGd1aWRlbGluZSBtZXRyaWNzIHN1Y2ggYXMgTVNFIHdpdGhpbiBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIHRvIGdpdmUgYSBzdWJqZWN0aXZlIG1lYXN1cmUgb2YgY2x1c3RlciBkZW5zaXR5LCBhbmQgc29tZSByb3VnaCBndWlkZWxpbmVzIGFyb3VuZCBjdXRzIGluIGRlbmRvZ3JhbXMsIGhvd2V2ZXIgdGhlcmUgaXMgc3RpbGwgYSBsYXJnZSBhbW91bnQgb2YgbWFudWFsIHdvcmsgaW52b2x2ZWQgaW4gaGFuZCBsYWJlbGxpbmcgdGhlIHJlc3VsdGluZyBjbHVzdGVycy4KVGhlIGhhbmQgbGFiZWxsaW5nIGNhbiBiZSBhc3Npc3RlZCBhIGdyZWF0IGRlYWwgYnkgdmlzdWFsaXNhdGlvbiBhbmQgc2NyaXB0aW5nIGNhcGFiaWxpdGllcyB0aGF0IGFsbG93IHJhcGlkIGV4cGxvcmF0aW9uIGFuZCByZWxhYmVsbGluZyBvZiBjbHVzdGVycy4gClRoaXMgZG9jdW1lbnQgcHJvdmlkZWQgYW4gZXhhbXBsZSBvZiBob3cgdGhpcyBjYW4gYmUgZG9uZSBpbiBSLgoK